10. Interoperability with C/C++
10.1 Calling C/C++ from Birdee
Birdee code can easily call C/C++ functions. You should first declare an external function in Birdee source code with the correct prototype. Then link the compiled Birdee object file with C/C++ object files or libraries. The syntax to declare an external function is
declare {function_name} [alias {"function_alias"}] ([parameters]) [as {return_type}]
For example, we can declare a C function "puts" which prints a string by:
declare function puts (str as pointer) as int
Note that the "puts" function in C takes an "char*" as argument. Here we use raw pointer type "pointer" as the type for this argument. We can then use this function to print the contents of a Birdee string:
puts("hello".get_raw())
The "get_raw" method of the string class of Birdee returns the raw pointer of the string data.
In C/C++ or other languages, the compiler may mangle the name of the function, making the function name "strange". You can use the "alias" clause of when declaring a function. For example, the linking name of an external function is "_add0", and you want to simply use the function with name "add", you can declare it by:
declare function add alias "_add0" () as int
You may need to pass pointers of Birdee-managed data to an external function. To get the pointer to an Birdee variable, you can use "addressof" keyword:
addressof(lvalue)
You can apply "addressof" on any "LValues", which have an address in the run time. Basically, variables and fields of class objects are LValues. Note that you can apply addressof on fields of structs only when the struct object itself is an LValue.
You can convert a reference to an object to raw pointer by "pointerof" keyword:
pointerof(reference)
The "reference" here is any valid reference expression (array or class objects). "addressof" and "pointerof" has different meanings. "addressof" gets the address of the variable itself. And "pointerof" gets the address of the object that the expression points to.
dim a as string = "hi"
dim p_a = addressof(a) # "p_a = &a" in C++
dim p_hi = pointerof(a) # "p_hi = (void*)a" in C++
You can get the pointer to the "real" data buffer of some built-in data structures. Birdee "string" and arrays has "get_raw" method to get the pointer of the internal buffer. Using pointerof on string and arrays to get "char*" or "int*" may not work correctly as you have expected!
10.2 Calling Birdee from C/C++
Birdee variables and functions can be used in C/C++ or other languages. Each Birdee function or variable has a unique name which is composed of "module_name" + "." + "func/variable name". For example, for a module "com.menooker.lib", it has a variable "a" and a function "func_a". Then the unique name of the variable and the function will be "com.menooker.lib.a" and "com.menooker.lib.func_a". The unique names for template functions in Birdee are constructed by appending the template parameter to the template names, and using "[" and "]" to mark the start and the end of the template parameters, just like how template functions are used in the source code. For example, the unique name of an instance of the function template "add_2" can be "com.menooker.lib.add[int,float]". Unique name is used internally in Birdee compiler. However, in C++ or C, you cannot declare a function name with a dot in it. Birdee use name mangling to transform the unique name of a variable or function to a valid name for C/C++. Mangled unique names are the names of global top-level variables and functions generated in the object file in Birdee. Simple rules are used to mangle a unique name - Replace any:
- "_" with "__"
- "." with "_0"
- "!" with "_1"
- "[" with "_2"
- "]" with "_3"
- "," with "_4"
- " " with "_5"
- other characters that are not in [a-z] or [A-Z] with "_xHH", where HH is the hexadecimal representation of the ASCII code of the character, e.g. "_x2h".
For example we have a Birdee module:
package com.menooker.lib
function add_2[T1,T2](a as T1,b as T1) as T2
return a+b
end
add_2[int,float] # declare an instance of the function template
dim g_value as int = 10
The unique name "com.menooker.lib.add_2[int,float]" will be transformed to "com_0menooker_0lib_0add__2_2int_4float_3". Similarly, the unique name "com.menooker.lib.g_value" will be transformed to "com_0menooker_0lib_0g__value".
Then, in C++ code, you can declare the function and the global variable by:
extern "C" void com_0menooker_0lib_0_1main();
extern "C" float com_0menooker_0lib_0add__2_2int_4float_3 (int,int);
extern int com_0menooker_0lib_0g__value;
Note 1: If you want to use this C++ declaration in C code, replace extern "C" with "extern".
Note 2: The function with unique name "{module_name}.!main" is the top-level "main function" code for the module. In the above example, we declare a function "com_0menooker_0lib_0_1main" for the top-level code. Remember to call the top-level "main function" code once before using the variables and the functions of the module. If the "main function" is not called, the module's global variables may not be correctly initialized. The top-level code of a Birdee module will call all top-level "main functions" of the modules it imports. So you don't need to manually call all module's top-level "main functions". Instead, you only need to call the "main functions" of several "root" modules. There is no effect to call a top-level "main function" multiple times after the first call to it - the "main function" will do nothing after it is called for the first time.